package cn.wolfcode.service.impl;

import cn.wolfcode.common.exception.BusinessException;
import cn.wolfcode.common.web.CodeMsg;
import cn.wolfcode.common.web.Result;
import cn.wolfcode.domain.Product;
import cn.wolfcode.domain.SeckillProduct;
import cn.wolfcode.domain.SeckillProductVo;
import cn.wolfcode.feign.ProductFeignApi;
import cn.wolfcode.mapper.SeckillProductMapper;
import cn.wolfcode.redis.SeckillRedisKey;
import cn.wolfcode.service.ISeckillProductService;
import cn.wolfcode.util.IdGenerateUtil;
import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.apache.rocketmq.spring.core.RocketMQTemplate;
import org.redisson.RedissonRedLock;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;


@Slf4j
@Service
public class SeckillProductServiceImpl implements ISeckillProductService {
    @Autowired
    private SeckillProductMapper seckillProductMapper;
    @Autowired
    private StringRedisTemplate redisTemplate;
    @Autowired
    private RocketMQTemplate rocketMQTemplate;
    @Autowired
    private ProductFeignApi productFeignApi;
    @Autowired
    private RedissonClient redissonClient;

    @Override
    public List<SeckillProductVo> queryByTime(Integer time) {
        // 1. 基于场次 time 查询秒杀商品信息
        List<SeckillProduct> seckillProductList = seckillProductMapper.queryCurrentlySeckillProduct(time);

        // 如果查询到的秒杀商品为空, 则直接返回
        if (seckillProductList == null || seckillProductList.size() == 0) {
            return Collections.emptyList();
        }

        // 拿到秒杀商品列表中所有的商品 id
        List<Long> productIdList = seckillProductList.stream().map(SeckillProduct::getProductId).collect(Collectors.toList());
        // 基于商品 id 集合 Feign远程调用商品 Controller 查询参与秒伤商品信息
        Result<List<Product>> productResult = productFeignApi.queryByIds(productIdList);
        // 如果远程调用出现异常, 则则直接抛出异常
        if (productResult.hasError()) {
            log.info("[秒杀商品查询]: 秒杀商品查询失败, 参数{}, 返回值{}", time, productResult);
            throw new BusinessException(new CodeMsg(productResult.getCode(), productResult.getMsg()));
        }

        // 将 Feign远程调用查询的参与秒杀商品信息存入 map, key = 商品 id, value = 商品信息
        Map<Long, Product> productMap = productResult.getData().stream().collect(Collectors.toMap(Product::getId, p -> p));
        return seckillProductList.stream().map(sp -> {
            SeckillProductVo vo = new SeckillProductVo();
            Product product = productMap.get(sp.getProductId());
            BeanUtils.copyProperties(product, vo);
            BeanUtils.copyProperties(sp, vo);
            return vo;
        }).collect(Collectors.toList());
    }

    @Override
    public List<SeckillProductVo> queryByTimeInCache(Integer time) {
        String redisKey = SeckillRedisKey.INIT_SECKILL_PROFUCT_LIST_STRING.getRealKey(time.toString());
        log.info("[秒杀商品] 从 redis 查询数据: key={}", redisKey);
        String json = redisTemplate.opsForValue().get(redisKey);
        if (StringUtils.isEmpty(json)) {
            List<SeckillProductVo> productVoList = queryByTime(time);
            if (productVoList != null && productVoList.size() > 0) {
                log.info("[秒杀商品] redis 秒杀列表不存在, 查询数据库并存入 redis: size={}", productVoList.size());
                redisTemplate.opsForValue().set(redisKey, JSON.toJSONString(productVoList));
                return productVoList;
            }
        }
        List<SeckillProductVo> list = JSON.parseArray(json, SeckillProductVo.class);
        for (SeckillProductVo vo : list) {
            setStockCount(vo);
        }
        return list;
    }

    @Override
    public SeckillProductVo queryById(Long id) {
        SeckillProduct seckillProduct = seckillProductMapper.selectById(id);
        Result<Product> result = productFeignApi.getById(seckillProduct.getProductId());
        if (result.hasError()) {
            log.warn("[商品详细信息] Feign远程查询商品详细失败, 参数{}, 结果{}", seckillProduct.getProductId(), JSON.toJSONString(result));
            throw new BusinessException(new CodeMsg(result.getCode(), result.getMsg()));
        }
        SeckillProductVo vo = new SeckillProductVo();
        BeanUtils.copyProperties(result.getData(), vo);
        BeanUtils.copyProperties(seckillProduct, vo);
        return vo;
    }

    @Override
    public SeckillProductVo queryByIdInCache(Long seckillId, Integer time) {
        String realKey = SeckillRedisKey.INIT_SECKILL_PROFUCT_DETAILS_HASH.getRealKey(time.toString());
        log.info("[秒杀商品] 查询秒商品详情, time={}, seckillId={}", time, seckillId);
        String json = (String) redisTemplate.opsForHash().get(realKey, seckillId.toString());
        if (StringUtils.isEmpty(json)) {
            SeckillProductVo vo = this.queryById(seckillId);
            if (vo != null) {
                log.info("[秒杀商品]  redis 商品不存在, 查询数据库, seckillId={}", seckillId);
                redisTemplate.opsForHash().put(realKey, seckillId.toString(), vo);
                return vo;
            }
        }

        // 更新库存
        SeckillProductVo vo = JSON.parseObject(json, SeckillProductVo.class);
        setStockCount(vo);
        return vo;
    }

    private void setStockCount(SeckillProductVo vo) {
        // 更新缓存
        String stockCount = (String) redisTemplate.opsForHash().get(SeckillRedisKey.SECKILL_STOCK_COUNT_HASH.getRealKey(vo.getTime().toString()), vo.getId().toString());

        if (stockCount != null && stockCount.length() > 0) {
            int stock = Integer.parseInt(stockCount);
            vo.setStockCount(Math.max(stock, 0));
        }
    }

    @Override
    public int decrStockCount(Long id) {
        String redisKey = IdGenerateUtil.get().nextId() + "" + id;
        SeckillProduct seckillProduct = seckillProductMapper.selectById(id);
        RLock lock1 = redissonClient.getLock(redisKey);  // redis 节点1
        RLock lock2 = redissonClient.getLock(redisKey);  // redis 节点2
        RLock lock3 = redissonClient.getLock(redisKey);  // redis 节点3
        RedissonRedLock redLock = new RedissonRedLock(lock1, lock2, lock3);
        try {
            //加锁
            redLock.lock();
            if (seckillProduct.getStockCount() > 0) {
                // 减库存
                return seckillProductMapper.decrStock(id);
            }
        } finally {
            // 释放锁
            redLock.unlock();
        }
        return 0;
    }

    @Override
    public void incrStockCount(Long seckillId) {
        seckillProductMapper.incrStock(seckillId);
    }
}
